package com.derbysoft.synxisadapter.dswitch2.translator.impl;

import com.derbysoft.core.utils.DateTimeUtils;
import com.derbysoft.remote.dswitch.core.SourceDTO;
import com.derbysoft.remote.dswitch.generic.constants.UniqueIDType;
import com.derbysoft.remote.dswitch.generic.dto.SimpleAmountDTO;
import com.derbysoft.remote.dswitch.generic.dto.UniqueIDDTO;
import com.derbysoft.remote.dswitch.hotel.constants.CancelPenaltyType;
import com.derbysoft.remote.dswitch.hotel.constants.FreeMealType;
import com.derbysoft.remote.dswitch.hotel.constants.MealsIncludedType;
import com.derbysoft.remote.dswitch.hotel.constants.PaymentType;
import com.derbysoft.remote.dswitch.hotel.dto.*;
import com.derbysoft.remote.share.Currency;
import com.derbysoft.remote.share.LanguageType;
import com.derbysoft.remote.share.dto.DateRangeDTO;
import com.derbysoft.remote.share.dto.TPAExtensionsDTO;
import com.derbysoft.synxisadapter.commons.dto.synxis.*;
import com.derbysoft.synxisadapter.commons.exception.ChannelNotFoundException;
import com.derbysoft.synxisadapter.commons.model.Channel;
import com.derbysoft.synxisadapter.commons.repository.ChannelRepository;
import com.derbysoft.synxisadapter.commons.repository.HotelRepository;
import com.derbysoft.synxisadapter.commons.utils.Constant;
import org.apache.commons.collections.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;

import static com.derbysoft.synxisadapter.dswitch2.translator.impl.TranslateHelper.setTax;

public abstract class AbstractRQTranslator extends ObjectFactory {

    public static final String RATE_RULE = "RATE_RULE";

    protected String provideCode;

    protected HotelRepository hotelRepository;

    protected ChannelRepository channelRepository;

    protected List<AvailRoomStayDTO> createRoomStayDTOs(StayDateRangeDTO stayDateRangeDTO, RoomStay roomStay,
                                                        Channel channel, int numberOfUnits) {
        ArrayList<AvailRoomStayDTO> availRoomStayDTOs = new ArrayList<AvailRoomStayDTO>();
        if (roomStay != null && roomStay.getRatePlans() != null && roomStay.getRoomTypes() != null) {
            Map<String, RatePlanDTO> ratePlanMap = createRatePlanMap(roomStay.getRatePlans().getRatePlan());
            Map<String, RoomTypePair> roomTypePairMap = createRoomTypeMap(roomStay.getRoomTypes().getRoomType());
            for (RoomRate roomRate : roomStay.getRoomRates().getRoomRate()) {
                RatePlanDTO ratePlanDTO = setTax(stayDateRangeDTO, channel, ratePlanMap.get(roomRate.getRatePlanCode()), roomRate);
                RoomTypePair roomTypePair = roomTypePairMap.get(roomRate.getRoomTypeCode());
                AvailRoomStayDTO unfilteredRoomStayDTO = createAvailRoomStayDTO(roomRate, ratePlanDTO, roomTypePair, channel);
                AvailRoomStayDTO availRoomStayDTO = TranslateHelper.filter(unfilteredRoomStayDTO, numberOfUnits);
                if (availRoomStayDTO != null) {
                    availRoomStayDTOs.add(availRoomStayDTO);
                }
            }
        }
        return availRoomStayDTOs;
    }

    private AvailRoomStayDTO createAvailRoomStayDTO(RoomRate roomRate, RatePlanDTO ratePlanDTO, RoomTypePair roomTypePair, Channel channel) {
        AvailRoomStayDTO availRoomStayDTO = new AvailRoomStayDTO();
        availRoomStayDTO.setRoomType(roomTypePair.getRoomTypeDTO());
        availRoomStayDTO.setRatePlan(ratePlanDTO);
        availRoomStayDTO.setRoomRate(createRoomRateDTO(roomRate, channel));
        if (roomTypePair.getRoomType().getNumberOfUnits() <= 1) {
            availRoomStayDTO.setQuantity(1);
        } else {
            availRoomStayDTO.setQuantity(roomTypePair.getRoomType().getNumberOfUnits());
        }
        availRoomStayDTO.setLanguageType(LanguageType.EN_US);
        availRoomStayDTO.setProviderChain(createProviderChainDTO());
        return availRoomStayDTO;
    }

    protected RoomRateDTO createRoomRateDTO(RoomRate roomRate, Channel channel) {
        RoomRateDTO roomRateDTO = new RoomRateDTO();
        roomRateDTO.setRates(createRateDTOs(roomRate.getRates().getRate(), channel));
        return roomRateDTO;
    }

    private ProviderChainDTO createProviderChainDTO() {
        ProviderChainDTO providerChainDTO = new ProviderChainDTO();
        providerChainDTO.setProviders(createProviders());
        return providerChainDTO;
    }

    private List<UniqueIDDTO> createProviders() {
        ArrayList<UniqueIDDTO> uniqueIDDTOs = new ArrayList<UniqueIDDTO>();
        uniqueIDDTOs.add(new UniqueIDDTO(provideCode, UniqueIDType.GROUP));
        return uniqueIDDTOs;
    }

    private List<RateDTO> createRateDTOs(List<RateType> rates, Channel channel) {
        ArrayList<RateDTO> rateDTOs = new ArrayList<RateDTO>();
        if (CollectionUtils.isNotEmpty(rates)) {
            RateType rateType = rates.get(0);
            if (rateType.getTpaExtensions() != null &&
                    rateType.getTpaExtensions().getNightlyRate() != null) {
                for (NightlyRate nightlyRate : rateType.getTpaExtensions().getNightlyRate()) {
                    rateDTOs.add(createRateDTO(rateType, nightlyRate, channel));
                }
            }
        }

        return rateDTOs;
    }

    private RateDTO createRateDTO(RateType rate, NightlyRate nightlyRate, Channel channel) {
        RateDTO rateDTO = new RateDTO();
        rateDTO.setPureAmount(createPureAmount(rate.getBase().getCurrencyCode(),
                channel.isInclusiveTaxRate() ? nightlyRate.getPriceWithTaxAndFee() : nightlyRate.getPrice()));
        rateDTO.setDateRange(createDateRangeDTO(DateTimeUtils.parse(nightlyRate.getDate(), "MM/dd/yyyy")));
        rateDTO.setMealsIncluded(new MealsIncludedDTO(MealsIncludedType.UNKNOWN));
        rateDTO.setDescription(translateDescription(nightlyRate));
        TPAExtensionsDTO tpaExtensions = new TPAExtensionsDTO();
        tpaExtensions.setElement(RATE_RULE, String.format("%s|%s|%s|%s", nightlyRate.getPrice(),
                nightlyRate.getTax(),
                nightlyRate.getFee(),
                nightlyRate.getPriceWithTaxAndFee()));
        rateDTO.setTpaExtensions(tpaExtensions);
        return rateDTO;
    }

    private String translateDescription(NightlyRate nightlyRate) {
        return String.format("Rate:%s Taxes:%s Fees:%s Total:%s",
                nightlyRate.getPrice(), nightlyRate.getTax(), nightlyRate.getFee(), nightlyRate.getPriceWithTaxAndFee());
    }

    private DateRangeDTO createDateRangeDTO(Date date) {
        return new DateRangeDTO(date, date);
    }

    private SimpleAmountDTO createPureAmount(String currencyCode, BigDecimal price) {
        return new SimpleAmountDTO(TranslateHelper.setScale(price), Currency.fromCode(currencyCode));
    }

    private Map<String, RatePlanDTO> createRatePlanMap(List<RatePlan> ratePlans) {
        Map<String, RatePlanDTO> roomTypeMap = new HashMap<String, RatePlanDTO>();
        if (CollectionUtils.isNotEmpty(ratePlans)) {
            for (RatePlan ratePlan : ratePlans) {
                roomTypeMap.put(ratePlan.getRatePlanCode(), createRatePlanDTO(ratePlan));
            }
        }
        return roomTypeMap;
    }

    private Map<String, RoomTypePair> createRoomTypeMap(List<RoomType> roomTypes) {
        Map<String, RoomTypePair> roomTypeMap = new HashMap<String, RoomTypePair>();
        if (CollectionUtils.isNotEmpty(roomTypes)) {
            for (RoomType roomType : roomTypes) {
                roomTypeMap.put(roomType.getRoomTypeCode(), new RoomTypePair(roomType, createRoomTypeDTO(roomType)));
            }
        }
        return roomTypeMap;
    }

    private RoomTypeDTO createRoomTypeDTO(RoomType roomType) {
        RoomTypeDTO roomTypeDTO = new RoomTypeDTO();
        roomTypeDTO.setCode(roomType.getRoomTypeCode());
        roomTypeDTO.setName(roomType.getRoomDescription().getName());
        if (roomType.getRoomDescription() != null) {
            roomTypeDTO.setDescription(roomType.getRoomDescription().getText());
        }
        return roomTypeDTO;
    }

    private RatePlanDTO createRatePlanDTO(RatePlan ratePlan) {
        RatePlanDTO ratePlanDTO = new RatePlanDTO();
        ratePlanDTO.setCode(ratePlan.getRatePlanCode());
        ratePlanDTO.setName(ratePlan.getRatePlanName());
        if (ratePlan.getRatePlanDescription() != null) {
            ratePlanDTO.setDescription(ratePlan.getRatePlanDescription().getText());
        }
        ratePlanDTO.setPaymentType(PaymentType.POA);
        ratePlanDTO.setRateType(com.derbysoft.remote.dswitch.hotel.constants.RateType.CASH);
        ratePlanDTO.setFreeMeal(createFreeMealDTO());
        ratePlanDTO.setCancelPolicy(createCancelPolicyDTO(ratePlan.getCancelPenalties()));
        return ratePlanDTO;
    }

    private CancelPolicyDTO createCancelPolicyDTO(ArrayOfCancelPenalty arrayOfCancelPenalty) {
        if (arrayOfCancelPenalty == null ||
                CollectionUtils.isEmpty(arrayOfCancelPenalty.getCancelPenalty())) {
            return null;
        }
        CancelPenalty cancelPenalty = arrayOfCancelPenalty.getCancelPenalty().get(0);
        CancelPolicyDTO cancelPolicyDTO = new CancelPolicyDTO();
        cancelPolicyDTO.setCancelPenaltyType(CancelPenaltyType.UNKNOWN);
        if (cancelPenalty.getPenaltyDescription() != null) {
            cancelPolicyDTO.setDescription(cancelPenalty.getPenaltyDescription().getText());
        }
        return cancelPolicyDTO;
    }

    private FreeMealDTO createFreeMealDTO() {
        FreeMealDTO freeMealDTO = new FreeMealDTO();
        freeMealDTO.setType(FreeMealType.UNKNOWN);
        return freeMealDTO;
    }

    private static final class RoomTypePair {
        private RoomType roomType;
        private RoomTypeDTO roomTypeDTO;

        private RoomTypePair(RoomType roomType, RoomTypeDTO roomTypeDTO) {
            this.roomType = roomType;
            this.roomTypeDTO = roomTypeDTO;
        }

        public RoomType getRoomType() {
            return roomType;
        }

        public RoomTypeDTO getRoomTypeDTO() {
            return roomTypeDTO;
        }
    }

    protected POS translatePOS(SourceDTO source) {
        POS pos = createPOS();
        pos.setSource(translateSource(findChannel(source.getUniqueID())));
        return pos;
    }

    private Source translateSource(Channel channel) {
        Source source = createSource();
        source.setRequestorId(translateRequeestorId(channel));
        return source;
    }

    private RequestorID translateRequeestorId(Channel channel) {
        RequestorID requestorID = createRequestorID();
        requestorID.setID(channel.getId());
        requestorID.setIDContext(Constant.CONTEXT_SYNXIS);
        requestorID.setCompanyName(translateCompanyName(channel));
        return requestorID;
    }

    private CompanyName translateCompanyName(Channel channel) {
        CompanyName companyName = createCompanyName();
        companyName.setCode(channel.getCode());
        return companyName;
    }

    protected ArrayOfGuestCount translateGuestCounts(List<GuestCountDTO> guestCountDTOs) {
        ArrayOfGuestCount arrayOfGuestCount = createArrayOfGuestCount();

        for (GuestCountDTO guestCountDTO : guestCountDTOs) {
            if (guestCountDTO.getCount() > 0) {
                arrayOfGuestCount.getGuestCount().add(translateGuestCount(guestCountDTO));
            }
        }

        return arrayOfGuestCount;
    }

    private GuestCount translateGuestCount(GuestCountDTO guestCountDTO) {
        GuestCount guestCount = createGuestCount();
        guestCount.setAgeQualifyingCode(guestCountDTO.getAgeQualifyingType().getCode());
        guestCount.setCount(guestCountDTO.getCount());
        return guestCount;
    }

    protected UniqueID translateUniqueID(String type, String id, String idContext) {
        UniqueID uniqueID = createUniqueID();
        uniqueID.setID(id);
        uniqueID.setType(type);
        uniqueID.setIDContext(idContext);
        return uniqueID;
    }

    protected Channel findChannel(UniqueIDDTO uniqueIDDTO) {
        Channel channel = channelRepository.findByPassport(uniqueIDDTO.getId());

        if (channel == null) {
            throw new ChannelNotFoundException(uniqueIDDTO.getId());
        }
        return channel;
    }

    public void setHotelRepository(HotelRepository hotelRepository) {
        this.hotelRepository = hotelRepository;
    }

    public void setChannelRepository(ChannelRepository channelRepository) {
        this.channelRepository = channelRepository;
    }

    public void setProvideCode(String provideCode) {
        this.provideCode = provideCode;
    }
}
